package com.yan.yanbi.service.impl;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yan.yanbi.bizmq.BiMqMessageProducer;
import com.yan.yanbi.common.ErrorCode;
import com.yan.yanbi.constant.*;
import com.yan.yanbi.exception.BusinessException;
import com.yan.yanbi.exception.ThrowUtils;
import com.yan.yanbi.manager.AiManager;
import com.yan.yanbi.manager.RedisLimiterManager;
import com.yan.yanbi.mapper.ChartMapper;
import com.yan.yanbi.model.dto.chart.ChartQueryRequest;
import com.yan.yanbi.model.dto.chart.GenChartByAiRequest;
import com.yan.yanbi.model.entity.Chart;
import com.yan.yanbi.model.entity.User;
import com.yan.yanbi.model.vo.BiResponse;
import com.yan.yanbi.service.ChartService;
import com.yan.yanbi.service.UserService;
import com.yan.yanbi.utils.ExcelUtils;
import com.yan.yanbi.utils.SqlUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author 86177
 * @description 针对表【chart(图表信息表)】的数据库操作Service实现
 * @createDate 2024-02-26 12:44:27
 */
@Service
public class ChartServiceImpl extends ServiceImpl<ChartMapper, Chart>
        implements ChartService {

    @Resource
    private UserService userService;

    @Resource
    private AiManager aiManager;

    @Resource
    private StringRedisTemplate stringRedisTemplate;//缓存图表数据

    @Resource
    private RedisLimiterManager redisLimiterManager;
    @Resource
    private ThreadPoolExecutor threadPoolExecutor;
    @Resource
    private BiMqMessageProducer biMqMessageProducer;

    /**
     * 异步生成图表
     *
     * @param multipartFile       上传的文件
     * @param genChartByAiRequest 生成的请求
     * @param request             登录请求
     * @return 生成的结果
     */
    @Override
    public BiResponse genChartAndSaveDb(MultipartFile multipartFile, GenChartByAiRequest genChartByAiRequest, HttpServletRequest request) {
        String chartName = genChartByAiRequest.getChartName();
        String goal = genChartByAiRequest.getGoal();
        String chartType = genChartByAiRequest.getChartType();
        // 校验
        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "图表分析目标为空");
        ThrowUtils.throwIf(StringUtils.isNotBlank(chartName) && chartName.length() > 200, ErrorCode.PARAMS_ERROR, "图表名称过长");
        ThrowUtils.throwIf(StringUtils.isBlank(chartType), ErrorCode.PARAMS_ERROR, "图表类型为空");

        User loginUser = userService.getLoginUser(request);
        fileCheck(multipartFile);

        //对智能分析接口进行限流
        redisLimiterManager.doRateLimit("genChartByAi_" + loginUser.getId());
        /*
         *  用户的输入:
         *  分析需求：
         *  分析网站用户的增长情况
         *  原始数据：
         *  日期，用户数
         *  1号，10
         *  2号，20
         *  3号，30m
         */
        //构建用户输入的需求
        StringBuilder userInput = new StringBuilder();//StringBuilder性能更高，但线程不安全，StringBuffer性能稍差但线程安全
        userInput.append("分析需求: ").append("\n");
        //拼接分析目标
        String userGoal = goal;
        //如果图表类型不为空
        if (StrUtil.isNotBlank(chartType)) {
            userGoal += "，请使用" + chartType;
        }
        //继续拼接
        userInput.append(userGoal).append("\n");
        userInput.append("原始数据：").append("\n");
        //将excel格式的数据压缩为csv
        String csvData = ExcelUtils.excelToCsv(multipartFile);
        userInput.append(csvData).append("\n");

        //先将图表保存到数据库中【不保存生成结果】
        Chart chart = saveDb(goal, csvData, chartName, chartType, loginUser);

        //在最终返回结果前提交一个任务
        //CompletableFuture runAsync创建异步对象,启动线程任务并使用自己的线程池
        CompletableFuture.runAsync(() -> {
            //先将图表任务状态改为"执行中"。等待执行完成后修改为已完成，保存执行结果
            Chart updateChart = new Chart();
            updateChart.setId(chart.getId());
            //把任务状态改为执行中
            updateChart.setChartStatus("runnnig");
            boolean result = this.updateById(updateChart);
            //如果提交失败，一般情况下可能数据库出现问题

            if (!result) {
                handleChartUpdateError(chart.getId(), "更新图表执行中的状态失败");
                return;
            }
            //调用AI接口
            String chartResult = aiManager.doChat(AiConstant.MODEL_ID, userInput.toString());
            //将分析出的代码和结论分开
            String[] splits = chartResult.split("【【【【【");
            if (splits.length < 2) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "AI 生成错误");
            }

            // 去掉生成结果的换行、空格等
            String genChart = splits[1].trim();
            String genResult = splits[2].trim();

            //调用AI得到结果后再更新一次数据库
            Chart genChartResult = new Chart();
            genChartResult.setId(chart.getId());
            genChartResult.setGenResult(genResult);
            genChartResult.setGenChart(genChart);
            genChartResult.setChartStatus("succeed");
            boolean updateResult = this.updateById(genChartResult);
            if (!updateResult) {
                handleChartUpdateError(chart.getId(), "更新图表执行中的状态失败");
            }
        }, threadPoolExecutor);


        // 返回到前端
        BiResponse biResponse = new BiResponse();
        biResponse.setChartId(chart.getId());
        return biResponse;
    }

    @Override
    public BiResponse genChartAsyncByMq(MultipartFile multipartFile, GenChartByAiRequest genChartByAiRequest, HttpServletRequest request) {
        String chartName = genChartByAiRequest.getChartName();
        String goal = genChartByAiRequest.getGoal();
        String chartType = genChartByAiRequest.getChartType();
        // 校验
        ThrowUtils.throwIf(StringUtils.isBlank(goal), ErrorCode.PARAMS_ERROR, "图表分析目标为空");
        ThrowUtils.throwIf(StringUtils.isNotBlank(chartName) && chartName.length() > 200, ErrorCode.PARAMS_ERROR, "图表名称过长");
        ThrowUtils.throwIf(StringUtils.isBlank(chartType), ErrorCode.PARAMS_ERROR, "图表类型为空");

        User loginUser = userService.getLoginUser(request);
        fileCheck(multipartFile);

        //对智能分析接口进行限流
        redisLimiterManager.doRateLimit("genChartByAi_" + loginUser.getId());
        //构建用户输入的需求
        StringBuilder userInput = new StringBuilder();//StringBuilder性能更高，但线程不安全，StringBuffer性能稍差但线程安全
        userInput.append("分析需求: ").append("\n");
        //拼接分析目标
        String userGoal = goal;
        //如果图表类型不为空
        if (StrUtil.isNotBlank(chartType)) {
            userGoal += "，请使用" + chartType;
        }
        //继续拼接
        userInput.append(userGoal).append("\n");
        userInput.append("原始数据：").append("\n");
        //将excel格式的数据压缩为csv
        String csvData = ExcelUtils.excelToCsv(multipartFile);
        userInput.append(csvData).append("\n");

        //先将图表保存到数据库中【不保存生成结果】
        Chart chart = saveDb(goal, csvData, chartName, chartType, loginUser);

        // 任务队列已满
        if (threadPoolExecutor.getQueue().size() > threadPoolExecutor.getMaximumPoolSize()) {
            throw new BusinessException(ErrorCode.SYSTEM_ERROR, "当前任务队列已满");
        }
        Long newChartId = chart.getId();
        //向消费者发送消息
        biMqMessageProducer.sendMessage(String.valueOf(newChartId));
        // 返回到前端
        BiResponse biResponse = new BiResponse();
        biResponse.setChartId(chart.getId());
        return biResponse;
    }


    /**
     * 图表更新错误的异常工具类
     *
     * @param chartId     更新图表的ID
     * @param execMessage 图表的执行信息
     */
    @Override
    public void handleChartUpdateError(long chartId, String execMessage) {
        Chart updateChartResult = new Chart();
        //如果图表更新失败
        updateChartResult.setId(chartId);
        //设置状态异常
        updateChartResult.setChartStatus("failed");
        //设置图表的执行信息失败
        updateChartResult.setExecMessage(execMessage);
        boolean updateResult = this.updateById(updateChartResult);
        if (!updateResult) {
            log.error("更新图表失败状态失败" + chartId + "," + execMessage);
        }
    }

    /**
     * 通过ID获取图表，走缓存
     *
     * @param id 图表ID
     * @return 对应ID的图表
     */
    @Override
    public Chart getChartById(long id) {
        if (id <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        String key = ChartConstant.CHART_REDIS_KEY + id;//存储在redis的key
        //先查有没有缓存
        String json = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(json)) {
            //若有缓存则直接返回
            return JSONUtil.toBean(json, Chart.class);
        }
        //若命中的缓存为空，则返回错误信息，解决缓存穿透
        if ("".equals(json)) {
            // 返回空值
            return null;
        }
        //缓存未命中则查询数据库
        Chart chart = this.getById(id);
        if (chart == null) {
            //不存在则将空值存入缓存
            stringRedisTemplate.opsForValue().set(key, "", RedisConstant.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return null;
        } else {
            //若存在则写入缓存
            stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(chart), RedisConstant.CACHE_CHART_TTL, TimeUnit.DAYS);
            return chart;
        }
    }


    /**
     * 根据 ChartQueryRequest 构建查询条件的包装类。
     *
     * @param chartQueryRequest 包含图表查询条件的请求对象。可包含图表ID、名称、目标、类型、用户ID、排序字段及排序方式等条件。
     * @return QueryWrapper<Chart> 构建好的图表查询条件包装对象。
     */
    @Override
    public QueryWrapper<Chart> getQueryWrapper(ChartQueryRequest chartQueryRequest) {
        QueryWrapper<Chart> queryWrapper = new QueryWrapper<>();
        // 如果请求对象为空，直接返回一个空的查询包装器
        if (chartQueryRequest == null) {
            return queryWrapper;
        }
        // 从请求对象中获取各项查询条件
        Long id = chartQueryRequest.getId();
        String name = chartQueryRequest.getChartName();
        String goal = chartQueryRequest.getGoal();
        String chartType = chartQueryRequest.getChartType();
        Long userId = chartQueryRequest.getUserId();
        String sortField = chartQueryRequest.getSortField();
        String sortOrder = chartQueryRequest.getSortOrder();

        // 根据条件构建查询包装器
        queryWrapper.eq(id != null && id > 0, "id", id); // ID条件
        queryWrapper.like(StringUtils.isNotBlank(name), "chartName", name); // 图表名称条件
        queryWrapper.eq(StringUtils.isNotBlank(goal), "goal", goal); // 目标条件
        queryWrapper.eq(StringUtils.isNotBlank(chartType), "chartType", chartType); // 图表类型条件
        queryWrapper.eq(ObjectUtils.isNotEmpty(userId), "userId", userId); // 用户ID条件
        queryWrapper.eq("isDelete", false); // 是否删除的条件
        // 排序条件处理
        queryWrapper.orderBy(SqlUtils.validSortField(sortField), sortOrder.equals(CommonConstant.SORT_ORDER_ASC),
                sortField);
        return queryWrapper;
    }


    private void fileCheck(MultipartFile multipartFile) {
        //获得文件的大小和原始文件名称
        long size = multipartFile.getSize();
        String originalFilename = multipartFile.getOriginalFilename();
        //若文件过大
        ThrowUtils.throwIf(size > BiConstant.FILE_MAX_SIZE, ErrorCode.PARAMS_ERROR, "文件大小超过最大限制");
        //利用FiLeUtils获取文件的后缀名
        String suffix = FileUtil.getSuffix(originalFilename);
        //若后缀名不在定义的常量合法的后缀名中则抛出异常
        ThrowUtils.throwIf(!BiConstant.VALID_FILE_SUFFIX.contains(suffix), ErrorCode.PARAMS_ERROR, "文件格式不合法");
    }


    @NotNull
    private Chart saveDb(String goal, String csvData, String chartName, String chartType, User loginUser) {
        // 插入数据到数据库
        Chart chart = new Chart();
        chart.setGoal(goal);
        chart.setChartData(csvData);
        chart.setChartName(chartName);
        chart.setChartType(chartType);
        chart.setUserId(loginUser.getId());
        chart.setChartStatus("wait");//将任务状态设置为排队
        boolean saveResult = this.save(chart);
        //若保存到数据库失败
        ThrowUtils.throwIf(!saveResult, ErrorCode.SYSTEM_ERROR, "图表保存失败");
        return chart;
    }


}




